#include "tbcore/base/logging.hpp"

#include <fstream>
#include <sstream>

#include "tbcore/base/mutex.hpp"
#include "tbcore/base/tostring.hpp"
#include "tbcore/base/global_initializer.hpp"

#if defined(TB_OS_WIN)
#include <windows.h>
#endif

TB_NAMESPACE_BEGIN

namespace {

  TB_INITIALIZER_GENERAL(InitLoggingSettings, TB_NO_PREREQUISITES, TB_DEFAULT_DEPENDS)
    (const InitializerContext& context) {
    InitLoggingSettings();
    return Result();
  }

AsyncLoggerInterfaceWPtr gLoggingProxy;
LoggingSettings gLoggingSettings;
OFStream gLoggingStream;
SpinMutex gLoggingMutex;

struct SeverityPair {
  LogSeverity severity;
  const TCHAR* str;
};

static const SeverityPair kSeverityPairArray[] = {
  { kLogVerbose, _T("[VERBOSE]") },
  { kLogKernel, _T("[KERNEL]") },
  { kLogInfo, _T("[INFO]") },
  { kLogError, _T("[ERROR]") },
  { kLogWarning, _T("[WARNING]") },
  { kLogFatal, _T("[FATAL]") },
  { kLogDebug, _T("[DEBUG]") },
};

String GetSeverityPrefix(LogSeverity severity) {
  for (int32 i = 0; i < TB_ARRAYSIZE(kSeverityPairArray); ++i) {
    if (kSeverityPairArray[i].severity == severity) {
      return kSeverityPairArray[i].str;
    }
  }
  return _T("[UNKNOWN]");
}

void LogHelper(LogSeverity serverity, const String& msg) {
  if (AsyncLoggerInterfacePtr proxy = gLoggingProxy.lock()) {
    proxy->AsyncAppendLog(GetSeverityPrefix(serverity) + msg);
  } else {
    if (gLoggingSettings.lockState == kLogLockFile) {
      SpinMutex::ScopedLock lock(gLoggingMutex);
      gLoggingStream << GetSeverityPrefix(serverity) + msg;
      gLoggingStream.flush();
    } else {
      gLoggingStream << GetSeverityPrefix(serverity) + msg;
      gLoggingStream.flush();
    }
  }

  if (kLogDebug == serverity) {
#if defined(TB_OS_WIN)
    OutputDebugString(msg.c_str());
#endif //TB_OS_WIN
    fprintf_ys(stdout, msg.c_str());
  }
}

} //namespace

LoggingSettings::LoggingSettings()
  :logPrecision(15)
  ,logTimestamp(true)
  ,logProcessId(false)
  ,logThreadId(false)
  ,logFileAndLine(false)
  ,logFile(_T("tb.log"))
  ,lockState(kLogLockFile)
  ,fileState(kLogDeleteExistFile) {}

struct LogMessage::LogMessageImpl {
  String msg_;
  LogSeverity severity_;
  String file_;
  int line_;
  bool isstringstream_;
};

LogMessage::LogMessage(const TCHAR* file, int line, LogSeverity severity)
  : impl_(new LogMessageImpl()) {
  impl_->file_ = file;
  impl_->line_ = line;
  impl_->severity_ = severity;
}

LogMessage::~LogMessage() {
  LogHelper(impl_->severity_, impl_->msg_);
  delete impl_;
}

void LogMessage::operator&(std::ostream& ss) {
  ss << "\n";
  impl_->msg_ = TB_STRING((static_cast<std::stringstream*>(&ss))->str());
}

void LogMessage::operator&(WOStream& ss) {
  ss << TB_TEXT_WIDE("\n");
  impl_->msg_ = TB_STRING((static_cast<WStringStream*>(&ss))->str());
}

std::stringstream ConstructStringStream() {
  std::locale glocale("");
  std::stringstream stream;
  stream.imbue(glocale);
  stream.precision(gLoggingSettings.logPrecision);
  return stream;
}

TB::WStringStream ConstructWStringStream() {
  std::locale glocale("");
  TB::WStringStream stream;
  stream.imbue(glocale);
  stream.precision(gLoggingSettings.logPrecision);
  return stream;
}

void SetAsyncLogger( AsyncLoggerInterfaceWPtr loggingProxy ) {
  gLoggingProxy = loggingProxy;
}

void InitLoggingSettings( const LoggingSettings& settings ) {
  gLoggingSettings = settings;
  gLoggingStream.close();
  gLoggingStream = OFStream(gLoggingSettings.logFile);
}

void CloseLogFile() {
  if (gLoggingStream.is_open()) {
     gLoggingStream.close();
  }
}

void AppendLog( LogSeverity severity, const TCHAR* msg ) {
  LogHelper(severity, msg);
}

TB_NAMESPACE_END
